10  Pandas Series和DataFrame创建

10.1 引言Pandas在金融数据分析中的核心地位

Pandas是Python数据分析的基石库,建立在NumPy之上,提供了专为数据处理设计的高级数据结构和分析工具。在金融领域,Pandas几乎是事实上的标准,从数据清洗到复杂的时间序列分析,从简单的统计计算到复杂的金融建模,Pandas都能胜任。

理论背景:Pandas的设计哲学

Pandas的设计深受R语言data.frame的影响,并结合了NumPy的高性能计算能力和Python的灵活性: - DataFrame: 二维表格数据结构(类似Excel表格) - Series: 一维带标签数组(类似DataFrame的单列) - Index: 行标签,支持时间序列索引 - 向量化操作: 类似NumPy的高性能运算

为什么Pandas优于Excel? 1. 处理大数据: Excel限制约100万行,Pandas几乎无限制 2. 可重复性: 代码可以重现,Excel操作难以追踪 3. 自动化: 可以构建自动化分析流程 4. 复杂计算: 支持复杂的分组、聚合、变换操作 5. 时间序列: 专门优化的时间序列处理能力

10.2 Pandas简介与安装

列表 10.1
# =============================================================================
# 题目:导入Pandas库并验证安装
# =============================================================================
# 本代码块演示Pandas库的标准导入方式,并验证其是否正常工作。
# 在金融数据分析中,Pandas是核心工具库,用于处理结构化数据。
# =============================================================================

# ==================== 导入Pandas库 ====================
import pandas as pd  # 导入Pandas库,使用pd作为别名(行业惯例)
import numpy as np  # 导入NumPy库,Pandas依赖NumPy进行数值计算

# ==================== 查看Pandas版本 ====================
print(f"Pandas版本: {pd.__version__}")  # 打印Pandas版本号,用于确认环境

# ==================== 测试Pandas是否正常工作 ====================
test_data = pd.Series([1, 2, 3, 4, 5])  # 创建测试Series对象
print(f"\n测试数据:\n{test_data}")  # 打印测试数据,验证Pandas功能正常
# 输出解释:显示包含整数1-5的Series,左侧为索引(0-4),右侧为值

安装命令:

# 使用conda
conda install pandas

# 使用pip
pip install pandas

10.3 Series一维带标签数组

10.3.1 创建Series

Series是Pandas的一维数据结构,类似NumPy数组但带标签索引。

语法: pd.Series(data, index=index, dtype=dtype, name=name)

列表 10.2
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一:创建股票涨跌幅Series
import pandas as pd
list_may=[-0.0035,-0.0338,0.0058,-0.0298]  # 定义列表list_may
name=['中国卫星','中国软件','中国银行','上汽集团']  # 定义列表name
s=pd.Series(list_may,index=name)  # 创建Series序列s
print(s)  # 输出任务一:创建股票涨跌幅Series

代码深度解析:

  1. Series的组成:

    • Values: 底层的数据(NumPy数组)
    • Index: 行标签(可以是字符串、数字、日期等)
    • Name: Series的名称(可选)
  2. 查看Series属性:

    print(f"值:\n{s.values}")
    print(f"索引:\n{s.index}")
    print(f"名称: {s.name}")
    print(f"数据类型: {s.dtype}")
  3. 索引的意义:

    • 快速查找: 类似字典的键值查找
    • 数据对齐: 自动按索引对齐运算
    • 时间序列: 支持日期时间索引

10.3.2 Series的基本操作

列表 10.3
# =============================================================================
# 题目:Series的基本操作、数据访问和统计分析
# =============================================================================
# 本代码块演示Series的常用操作,包括索引访问、条件筛选、统计摘要和排序。
# 金融应用场景:分析股票涨跌幅,筛选负收益股票,计算统计指标。
# =============================================================================

# ==================== 创建示例Series ====================
s = pd.Series(  # 创建Series对象
    [-0.0035, -0.0338, 0.0058, -0.0298],  # 数据:四只股票涨跌幅
    index=['中国卫星', '中国软件', '中国银行', '上汽集团'],  # 索引:股票名称
    name='涨跌幅'  # Series名称
)

print("完整Series:")  # 打印标题
print(s)  # 显示完整的Series对象

# ==================== 1. 通过索引访问单个元素 ====================
print(f"\n中国软件涨跌幅: {s['中国软件']:.4f}")  # 使用索引标签访问单个元素
# 输出解释:.4f格式化为保留4位小数,显示-0.0338

# ==================== 2. 通过位置访问(类似NumPy) ====================
print(f"第一个元素: {s[0]:.4f}")  # 使用整数位置访问第一个元素
print(f"前两个元素:\n{s[:2]}")  # 使用切片访问前两个元素(位置0和1)
# 输出解释:显示中国卫星和中国软件的涨跌幅

# ==================== 3. 布尔索引(条件筛选) ====================
print(f"\n负收益股票:\n{s[s < 0]}")  # 使用布尔条件筛选负收益股票
# 输出解释:显示涨跌幅小于0的三只股票(中国卫星、中国软件、上汽集团)

# ==================== 4. 统计摘要 ====================
print(f"\n统计摘要:")
print(s.describe())  # 显示描述性统计信息
# 输出解释:包含count(数量)、mean(均值)、std(标准差)、min(最小值)、
#          25%(下四分位数)、50%(中位数)、75%(上四分位数)、max(最大值)

# ==================== 5. 向量化运算 ====================
s_doubled = s * 2  # Series乘以标量,所有元素都乘以2(向量化操作)
print(f"\n涨跌幅翻倍:")
print(s_doubled)
# 输出解释:每个涨跌幅值都翻倍,如-0.0035变为-0.0070

# ==================== 6. 排序 ====================
s_sorted = s.sort_values()  # 按值升序排序(默认)
print(f"\n按值排序(升序):")
print(s_sorted)
# 输出解释:从最负到最正排序,中国软件(-0.0338)排在最前

s_sorted_desc = s.sort_values(ascending=False)  # 按值降序排序
print(f"\n按值排序(降序):")
print(s_sorted_desc)
# 输出解释:从最正到最负排序,中国银行(0.0058)排在最前

补充说明:索引 vs 位置

访问方式 语法 说明 示例
标签索引 s['label'] 使用索引名 s['中国卫星']
位置索引 s[0] 使用整数位置 s[0]
切片(标签) s['a':'c'] 包含两端 s['A':'C']
切片(位置) s[0:2] 不包含末端 s[0:2]

易混淆点: 标签切片包含末端,位置切片不包含!

10.4 DataFrame二维表格数据

10.4.1 创建DataFrame

DataFrame是Pandas的二维数据结构,类似Excel表格或SQL表。

语法: pd.DataFrame(data, index=index, columns=columns)

列表 10.4
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务二:创建股票涨跌幅DataFrame
import pandas as pd
data={  # 定义data字典,开始构建键值映射
    '中国卫星':[-0.0351,0.0172,-0.0035,-0.0246,0.0394],  # "中国卫星"的数据序列
    '中国软件':[-0.0139,0.0243,-0.0338,0.0146,0.0001],  # "中国软件"的数据序列
    '中国银行':[-0.0139,0.0243,-0.0338,0.0146,0.0001],  # "中国银行"的数据序列
    '上汽集团':[0.0212,0.0021,-0.0298,-0.0027,-0.0143]  # "上汽集团"的数据序列
}  # 数据结构定义结束
index=['20200525','20200526','20200527','20200528','20200529']  # 定义列表index
df=pd.DataFrame(data,index)  # 创建数据框df
print(df)  # 输出数据框数据

代码深度解析:

  1. DataFrame的结构:

    DataFrame = {
        Index: 行标签(日期、ID等)
        Columns: 列名(变量名)
        Values: 二维NumPy数组
    }
  2. 从字典创建: 键自动成为列名

  3. 设置行索引:

    • 字符串: 日期、股票代码等
    • DatetimeIndex: 时间序列(推荐)
    • RangeIndex: 默认整数索引

10.4.2 其他创建DataFrame的方法

列表 10.5
# =============================================================================
# 题目:演示创建DataFrame的其他常用方法
# =============================================================================
# 本代码块演示从列表、元组、字典列表和NumPy数组创建DataFrame。
# 金融应用场景:根据数据来源不同,灵活选择创建方法。
# =============================================================================

# ==================== 方法1:从列表的列表创建 ====================
data_list = [  # 列表的列表,每个子列表代表一行数据
    ['中信证券', 24.78, 0.05],  # 第1行:名称、价格、涨跌幅
    ['国泰君安', 20.15, -0.02], # 第2行:名称、价格、涨跌幅
    ['海通证券', 14.03, 0.03]   # 第3行:名称、价格、涨跌幅
]
df1 = pd.DataFrame(data_list, columns=['名称', '价格', '涨跌幅'])  # 指定列名
print("方法1 - 从列表的列表:")
print(df1)
# 输出解释:每行代表一只证券,列分别为名称、价格、涨跌幅

# ==================== 方法2:从元组列表创建 ====================
data_tuples = [  # 元组列表,每个元组代表一行数据
    ('600519.SH', '贵州茅台', 1850.00),  # 第1行:代码、名称、价格
    ('000858.SZ', '五粮液', 220.50),     # 第2行:代码、名称、价格
    ('600036.SH', '招商银行', 45.20)     # 第3行:代码、名称、价格
]
df2 = pd.DataFrame(data_tuples, columns=['代码', '名称', '价格'])  # 指定列名
print("\n方法2 - 从元组列表:")
print(df2)
# 输出解释:每行代表一只股票,包含代码、名称和价格信息

# ==================== 方法3:从字典列表创建 ====================
data_dict_list = [  # 字典列表,每个字典代表一行数据
    {'code': '600519.SH', 'name': '贵州茅台', 'price': 1850.00},  # 第1行数据
    {'code': '000858.SZ', 'name': '五粮液', 'price': 220.50},      # 第2行数据
    {'code': '600036.SH', 'name': '招商银行', 'price': 45.20}      # 第3行数据
]
df3 = pd.DataFrame(data_dict_list)  # 字典的键自动成为列名
print("\n方法3 - 从字典列表:")
print(df3)
# 输出解释:字典的键(code, name, price)自动成为列名

# ==================== 方法4:从NumPy数组创建 ====================
arr = np.random.randn(5, 3)  # 生成5行3列的标准正态分布随机数
df4 = pd.DataFrame(  # 从NumPy数组创建DataFrame
    arr,  # 数据源
    index=['row1', 'row2', 'row3', 'row4', 'row5'],  # 自定义行索引
    columns=['A', 'B', 'C']  # 自定义列名
)
print("\n方法4 - 从NumPy数组:")
print(df4)
# 输出解释:5行3列的随机数表,行为row1-row5,列为A-C

10.5 DataFrame的基本操作

10.5.1 访问数据

列表 10.6
# =============================================================================
# 题目:演示访问DataFrame中数据的多种方法
# =============================================================================
# 本代码块演示如何访问DataFrame的列、行和单个值。
# 金融应用场景:提取特定股票数据、查询特定日期的数据、获取单个价格值。
# =============================================================================

# ==================== 重新创建示例DataFrame ====================
data = {  # 准备字典数据
    '中国卫星': [-0.0351, 0.0172, -0.0035, -0.0246, 0.0394],
    '中国软件': [-0.0139, 0.0243, -0.0338, 0.0146, 0.0001],
    '中国银行': [-0.0139, 0.0243, -0.0338, 0.0146, 0.0001],
    '上汽集团': [0.0212, 0.0021, -0.0298, -0.0027, -0.0143]
}
index = ['20200525', '20200526', '20200527', '20200528', '20200529']  # 日期索引
df = pd.DataFrame(data, index=index)  # 创建DataFrame

# ==================== 1. 访问列(推荐方式) ====================
print("方法1 - 使用点号(列名无空格):")
print(df.中国卫星.head(3))  # 使用点号访问列,head(3)显示前3行
# 输出解释:显示中国卫星列的前3个值(-0.0351, 0.0172, -0.0035)

print("\n方法2 - 使用方括号(通用):")
print(df['中国软件'].head(3))  # 使用方括号访问列(通用方法)
# 输出解释:显示中国软件列的前3个值

print("\n方法3 - 同时访问多列:")
print(df[['中国卫星', '中国软件']].head(3))  # 使用列表访问多列
# 输出解释:显示前3行,包含中国卫星和中国软件两列

# ==================== 2. 访问行 ====================
print("\n使用loc访问行(按标签):")
print(df.loc['20200526'])  # 使用loc按标签访问单行
# 输出解释:显示20200526这一行的所有列数据

print(df.loc[['20200526', '20200527']])  # 使用loc按标签访问多行
# 输出解释:显示20200526和20200527两行的所有列数据

print("\n使用iloc访问行(按位置):")
print(df.iloc[0])  # 使用iloc按位置访问第1行(位置0)
# 输出解释:显示第1行(20200525)的所有列数据

print(df.iloc[0:2])  # 使用iloc按位置访问前2行
# 输出解释:显示前2行(位置0和1)

# ==================== 3. 访问单个值 ====================
print("\n访问单个值:")
print(f"df.loc['20200526', '中国卫星'] = {df.loc['20200526', '中国卫星']:.4f}")  # 使用loc访问
# 输出解释:显示20200526这一天中国卫星的涨跌幅(0.0172)

print(f"df.iloc[0, 0] = {df.iloc[0, 0]:.4f}")  # 使用iloc访问
# 输出解释:显示第1行第1列的值(-0.0351)

10.5.2 DataFrame的基本信息

列表 10.7
# =============================================================================
# 题目:查看DataFrame的结构信息和统计摘要
# =============================================================================
# 本代码块演示如何快速了解DataFrame的基本情况。
# 金融应用场景:数据探索性分析(EDA),检查数据质量,了解数据分布。
# =============================================================================

print("DataFrame形状(行, 列):")
print(df.shape)  # 返回元组(行数, 列数)
# 输出解释:(5, 4)表示5行4列

print("\n前3行数据:")
print(df.head(3))  # 显示前3行数据
# 输出解释:显示20200525-20200527共3行的所有列数据

print("\n后3行数据:")
print(df.tail(3))  # 显示后3行数据
# 输出解释:显示20200527-20200529共3行的所有列数据

print("\n数据类型:")
print(df.dtypes)  # 显示每列的数据类型
# 输出解释:所有列都是float64类型(浮点数)

print("\n基本统计信息:")
print(df.describe())  # 显示描述性统计(均值、标准差、最小值、分位数、最大值)
# 输出解释:对每列计算count、mean、std、min、25%、50%、75%、max

print("\nDataFrame信息:")
print(df.info())  # 显示DataFrame的详细信息(包括内存使用)
# 输出解释:显示列名、非空值数量、数据类型、内存占用等

print("\n转置DataFrame:")
print(df.T)  # 转置DataFrame(行列互换)
# 输出解释:原来的行变成列,原来的列变成行

10.5.3 数据筛选与查询

列表 10.8
# =============================================================================
# 题目:使用条件表达式筛选DataFrame数据
# =============================================================================
# 本代码块演示如何根据条件筛选DataFrame的行。
# 金融应用场景:找出涨幅为正的交易日,找出满足多个条件的记录。
# =============================================================================

# ==================== 1. 条件筛选 ====================
print("中国卫星涨幅超过0的交易日:")
positive = df[df.中国卫星 > 0]  # 使用布尔条件筛选行
print(positive)
# 输出解释:显示中国卫星涨跌幅大于0的两个交易日(20200526, 20200529)

# ==================== 2. 多条件筛选 ====================
print("\n中国卫星>0 且 中国软件>0的交易日:")
multi_condition = df[(df.中国卫星 > 0) & (df.中国软件 > 0)]  # 使用&连接条件(且)
print(multi_condition)
# 输出解释:显示两只股票同时上涨的交易日

# ==================== 3. 使用query方法(更直观) ====================
print("\n使用query筛选:")
query_result = df.query('中国卫星 > 0 and 中国软件 > 0')  # 使用query方法
print(query_result)
# 输出解释:结果与上一步相同,但语法更直观

# ==================== 4. 选择特定列 ====================
print("\n选择特定列:")
selected = df[['中国卫星', '上汽集团']]  # 选择两列
print(selected.head())  # 显示前几行
# 输出解释:只显示中国卫星和上汽集团两列的数据

10.6 Series与DataFrame的运算

10.6.1 算术运算

Pandas支持向量化的算术运算,并自动对齐索引。

列表 10.9
# =============================================================================
# 题目:演示Pandas的向量化算术运算和索引对齐机制
# =============================================================================
# 本代码块演示Series和DataFrame的算术运算,以及Pandas强大的索引对齐功能。
# 金融应用场景:计算股票收益率差,计算投资组合收益,计算价格变化。
# =============================================================================

# ==================== 创建两个Series ====================
s1 = pd.Series([10, 20, 30], index=['a', 'b', 'c'])  # 第1个Series
s2 = pd.Series([5, 15, 25], index=['a', 'b', 'd'])  # 第2个Series(注意索引不同)

print("Series 1:")
print(s1)
print("\nSeries 2:")
print(s2)
# 输出解释:s1索引为a,b,c;s2索引为a,b,d

# ==================== 基本运算(自动对齐索引) ====================
print("\n加法(自动对齐):")
print(s1 + s2)
# 输出解释:a:15(a对a), b:35(b对b), c:NaN(c无对应), d:NaN(d无对应)
#          索引对齐机制:只有相同索引的值才会相加,不同的产生NaN

print("\n减法:")
print(s1 - s2)
# 输出解释:a:5, b:5, c:NaN, d:NaN

print("\n乘法:")
print(s1 * s2)
# 输出解释:a:50, b:300, c:NaN, d:NaN

print("\n除法:")
print(s1 / s2)
# 输出解释:a:2.0, b:1.33, c:NaN, d:NaN

# ==================== DataFrame运算 ====================
df1 = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})  # 第1个DataFrame
df2 = pd.DataFrame({'A': [10, 20, 30], 'B': [40, 50, 60]})  # 第2个DataFrame

print("\nDataFrame加法:")
print(df1 + df2)
# 输出解释:A列:[11, 22, 33], B列:[44, 55, 66]

# ==================== 标量运算(广播) ====================
print("\nDataFrame乘以标量:")
print(df1 * 10)  # DataFrame乘以标量,所有元素都乘以10
# 输出解释:A列:[10, 20, 30], B列:[40, 50, 60]

10.6.2 统计函数

列表 10.10
# =============================================================================
# 题目:使用Pandas统计函数进行金融数据分析
# =============================================================================
# 本代码块演示常用的统计函数,以及按行或按列计算。
# 金融应用场景:计算平均收益率、波动率、累积收益率等关键指标。
# =============================================================================

print("每只股票的统计信息:")

# ==================== 按列计算(每只股票所有交易日) ====================
print("\n均值:")
print(df.mean())  # 计算每列的平均值(默认axis=0)
# 输出解释:显示每只股票5个交易日的平均涨跌幅

print("\n标准差:")
print(df.std())  # 计算每列的标准差(衡量波动性)
# 输出解释:标准差越大,股票波动越大

print("\n最大值:")
print(df.max())  # 计算每列的最大值
# 输出解释:显示每只股票最大的单日涨幅

print("\n最小值:")
print(df.min())  # 计算每列的最小值
# 输出解释:显示每只股票最大的单日跌幅

print("\n中位数:")
print(df.median())  # 计算每列的中位数
# 输出解释:排序后位于中间的值

# ==================== 按行计算(每交易日所有股票) ====================
print("\n按行计算(横向):")
print(df.mean(axis=1))  # axis=1表示沿行方向计算(每行的均值)
# 输出解释:每个交易日4只股票的平均涨跌幅

# ==================== 按列计算(每只股票所有交易日) ====================
print("\n按列计算(纵向):")
print(df.mean(axis=0))  # axis=0表示沿列方向计算(每列的均值,默认)
# 输出解释:与df.mean()相同,每只股票的平均涨跌幅

# ==================== 累积收益率计算 ====================
print("\n累积收益率:")
cum_returns = (1 + df).cumprod() - 1  # 计算累积收益率
print(cum_returns)
# 输出解释:
# (1+df)将涨跌幅转换为收益率因子(如-0.01变为0.99)
# cumprod()计算累积乘积
# -1将因子转换回收益率
# 最终结果是累积收益率(假设初始投资为1)

10.7 缺失值处理

列表 10.11
# =============================================================================
# 题目:演示缺失值的检测、删除和填充方法
# =============================================================================
# 本代码块演示如何处理数据中的缺失值(NaN)。
# 金融应用场景:处理停牌日的缺失价格,填充股票未交易日的数据。
# =============================================================================

# ==================== 创建包含缺失值的DataFrame ====================
df_with_nan = pd.DataFrame({  # 创建包含NaN的DataFrame
    'A': [1, 2, np.nan, 4],      # 第3个值为NaN
    'B': [5, np.nan, np.nan, 8], # 第2、3个值为NaN
    'C': [9, 10, 11, 12]         # 无缺失值
})

print("包含缺失值的DataFrame:")
print(df_with_nan)
# 输出解释:NaN表示Not a Number,即缺失值

# ==================== 1. 检测缺失值 ====================
print("\n缺失值位置:")
print(df_with_nan.isnull())  # 检测每个值是否为缺失值
# 输出解释:True表示缺失值,False表示有效值

# ==================== 2. 删除缺失值 ====================
print("\n删除包含缺失值的行:")
print(df_with_nan.dropna())  # 删除包含任何缺失值的行
# 输出解释:只保留第1行和第4行(完整无缺失)

print("\n删除包含缺失值的列:")
print(df_with_nan.dropna(axis=1))  # 删除包含任何缺失值的列
# 输出解释:只保留C列(完全无缺失)

# ==================== 3. 填充缺失值 ====================
print("\n用0填充:")
print(df_with_nan.fillna(0))  # 用0填充所有缺失值
# 输出解释:所有NaN都被替换为0

print("\n用前向填充填充:")
print(df_with_nan.fillna(method='ffill'))  # 前向填充(用前一个值填充)
# 输出解释:A列第3行用2填充,B列第3行用5填充,第4行仍为NaN

print("\n用均值填充:")
print(df_with_nan.fillna(df_with_nan.mean()))  # 用每列的均值填充
# 输出解释:A列用均值2.33填充,B列用均值6.5填充